﻿#region License and Copyright Notice

//XOML: XML based object persistence framework

//Copyright (c) 2008 Ananth B.
//All rights reserved.

//The contents of this file are made available under the terms of the
//Eclipse Public License v1.0 (the "License") which accompanies this
//distribution, and is available at the following URL:
//http://www.opensource.org/licenses/eclipse-1.0.php

//Software distributed under the License is distributed on an "AS IS" basis,
//WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
//the specific language governing rights and limitations under the License.

//By using this software in any fashion, you are agreeing to be bound by the
//terms of the License.

#endregion

using System;
using System.Globalization;
using System.Reflection;

namespace Xoml
{
    public abstract class MarkupExtension
    {
        // Override this to process the whole tree after load is complete.
        // This is called ONCE irrespective of how many times the extension is used
        // This method is NOT called for extensions not used in the current Xoml
        // The order in which different extensions are called is not defined
        protected internal virtual void OnAfterLoad(AfterLoadEventArgs e) 
        {
            // Don't do anything
        }
        
        internal virtual PropertyInfo DefaultArgument
        {
            get
            {
                Type type = GetType();
                if (type.IsDefined(typeof(MarkupExtensionDefaultArgumentAttribute), false))
                {
                    string propertyName = (type.GetCustomAttributes(typeof(MarkupExtensionDefaultArgumentAttribute), false) as MarkupExtensionDefaultArgumentAttribute[])[0].Name;
                    PropertyInfo result = type.GetProperty(propertyName);
                    if (result == null)
                        throw new PropertyNotFoundException(propertyName, type);
                    else
                        return result;
                }
                else
                    return null;
            }
        }

        internal PropertyInfo GetProperty(string name)
        {
            PropertyInfo result = GetType().GetProperty(name);
            if (result == null)
                throw new PropertyNotFoundException(name, GetType());

            return result;
        }
        
        public MarkupExtension() // This is the  constructor that will always be called
        {
        }

        public abstract object ProvideValue(object context, PropertyInfo property, out bool defer);
        
        public virtual object DeferredProvideValue(object root, object context, PropertyInfo property) // This method is called if defer returned true, or this was a nested extension inside an expression that got deferred
        {
            throw new NotImplementedException(string.Format(CultureInfo.InvariantCulture, "If defer returns true or a complex markup expression was deferred by any operand, then {0} must implement DeferredProvideValue", GetType().Name));
        }

        public virtual Type ReturnType
        {
            get
            {
                // Get the return type. If not defined, it is "System.Object"
                return GetType().IsDefined(typeof(MarkupExtensionReturnTypeAttribute), false) ?
                    (GetType().GetCustomAttributes(typeof(MarkupExtensionReturnTypeAttribute), false) as MarkupExtensionReturnTypeAttribute[])[0].Type
                    : typeof(object);
            }
        }
    }

    public sealed class AfterLoadEventArgs: EventArgs
    {
        private object _root;
        
        public AfterLoadEventArgs(object root)
        {
            if (root == null)
                throw new ArgumentNullException("root");
            _root = root;
        }

        public object Root
        {
            get
            {
                return _root;
            }
            set
            {
                _root = value; // Settable
            }
        }
    }

    [AttributeUsage(AttributeTargets.Class, AllowMultiple=false, Inherited=false)]
    public sealed class MarkupExtensionDefaultArgumentAttribute : Attribute
    {
        private readonly string _name = string.Empty;
        
        public MarkupExtensionDefaultArgumentAttribute(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            _name = name;
        }

        public string Name
        {
            get
            {
                return _name;
            }
        }
    }

    // Defined only for compatibility with existing code. Override ReturnType and use that instead
    [AttributeUsage(AttributeTargets.Class, AllowMultiple=false, Inherited=false)]
    public sealed class MarkupExtensionReturnTypeAttribute: Attribute
    {
        private readonly Type _type;
        
        public MarkupExtensionReturnTypeAttribute(Type type)
        {
            if (type == null)
                throw new ArgumentNullException("type");
            _type = type;
        }

        public Type Type
        {
            get
            {
                return _type;
            }
        }
    }
}
